#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#if defined(__APPLE__) || defined(MACOSX)
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include "modelado.h"
#include "texturas.h"

/*--------------------------------------------------------------------------
 Devuelve el punto 3D asociado a unos determinados datos.
 R1: radio del primer anillo.
 y1: altura del primer anillo.
 P: el n�mero total de tramos en el anillo.
 N: el tramo actual.
 --------------------------------------------------------------------------*/
Punto3D vertice(float R1, float y1, int P, int N) {

	Punto3D p;

	p.x = dameX(R1, P, N);
	p.y = y1;
	p.z = dameZ(R1, P, N);

	return (p);
}

/*--------------------------------------------------------------------------
 Devuelve la normal de un v�rtice sobre una superficie de revoluci�n.
 R1: radio del primer anillo.
 R2: radio del segundo anillo.
 y1: altura del primer anillo.
 y2: altura del segundo anillo.
 P: el n�mero total de tramos en el anillo.
 N: el tramo actual.
 --------------------------------------------------------------------------*/
Vector3D normal(float R1, float R2, float y1, float y2, int P, int N) {
	Punto3D p, q, r;
	Vector3D v, w, normal;

	p = vertice(R1, y1, P, N);
	if (R1 == 0.0f && R2 == 0.0f) {
		normal.a = normal.c = 0.0f;
		normal.b = 1.0f;
		return (normal);
	}
	if (R1 == 0.0f) {
		q = vertice(R2, y2, P, (N + 1) % P);
	} else {
		q = vertice(R1, y1, P, (N + 1) % P);
	}
	r = vertice(R2, y2, P, N);
	v = vector(p, q);
	w = vector(p, r);
	normal = productoVectorial(w, v);

	return (normal);
}

/*-------------------------------------------------------------------------- 
 Dibuja una tira de cuadril�teros.
 R1, R2, R3: radios de trea anillos consecutivos.
 y1, y2, y3: altura de cada uno de los anillos.
 P: n�mero de puntos sobre cada anillo.
 --------------------------------------------------------------------------*/
void tira(float R1, float R2, float R3, float y1, float y2, float y3, int P) {
	int i;
	Punto3D p;
	Vector3D n;

	glBegin(GL_QUAD_STRIP);
	for (i = 0; i <= P; i++) {
		n = normal(R1, R2, y1, y2, P, i);
		glNormal3f(n.a, n.b, n.c);
		glTexCoord2f((float) i / P, y1 + 0.5);
		p = vertice(R1, y1, P, i);
		glVertex3f(p.x, p.y, p.z);
		n = normal(R2, R3, y2, y3, P, i);
		glNormal3f(n.a, n.b, n.c);
		glTexCoord2f((float) i / P, y2 + 0.5);
		p = vertice(R2, y2, P, i);
		glVertex3f(p.x, p.y, p.z);
	}
	glEnd();
}

/*-------------------------------------------------------------------------- 
 Algoritmo de dibujo para una forma s�lida de revoluci�n.
 H: la altura de la figura.
 N: el n�mero de anillos

 P: n�mero de  divisiones en cada anillo.
 f(float y): puntero a la funci�n que nos devuelve.
 el valor de radio en funci�n de y.

 -----------------------------------------------------------------------*/

void formaSolida(float H, int N, int P, float (*f)(float y)) {
	int i;
	float y1, y2, y3, r1, r2, r3;

	tapa(f(-H / 2), -H / 2, N, 0);
	for (i = 0; i < N - 1; i++) {
		y1 = i * H / (N - 1) - (H / 2);
		r1 = f(y1);
		y2 = (i + 1) * H / (N - 1) - (H / 2);
		r2 = f(y2);
		if (i + 2 < N) {
			y3 = (i + 2) * H / (N - 1) - (H / 2);
			r3 = f(y3);
		} else {
			r3 = r2;
			y3 = y2;
		}
		tira(r1, r2, r3, y1, y2, y3, P);
	}
	tapa(f(H / 2), H / 2, N, 1);
}

/*--------------------------------------------------------------------------
 Devuelve el valor de x del punto que gira.
 R: el radio de giro.
 N: el n�mero total de tramos en el anillo.
 n: el tramo actual.
 --------------------------------------------------------------------------*/
float dameX(float R, int N, int n) {
	float x = (float) R * cos(n * (2 * M_PI) / N);
	return x;
}

/*--------------------------------------------------------------------------
 Devuelve el valor de z del punto que gira.
 R: el radio de giro.
 N: el n�mero total de tramos en el anillo.
 n: el tramo actual.
 --------------------------------------------------------------------------*/
float dameZ(float R, int N, int n) {
	float z = (float) R * sin(n * (2 * M_PI) / N);
	return z;
}

/*--------------------------------------------------------------------------
 Devuelve el valor de x del punto que gira.
 R: el radio de giro.
 N: el n�mero total de tramos en el anillo.
 n: el tramo actual.
 --------------------------------------------------------------------------*/
float dameRandomX(float R, int N, int n) {
	float x = (float) R * cos(n * (2 * M_PI) / N);
	return x;
}

/*--------------------------------------------------------------------------
 Devuelve el valor de z del punto que gira.
 R: el radio de giro.
 N: el n�mero total de tramos en el anillo.
 n: el tramo actual.
 --------------------------------------------------------------------------*/
float dameRandomZ(float R, int N, int n) {
	float z = (float) R * sin(n * (2 * M_PI) / N);
	return z;
}

/*--------------------------------------------------------------------------
 Dibuja un anillo.
 R: el radio del anillo.
 y: la altura a la que se dibuja el anillo.
 N: el n�mero de tramos del anillo.
 --------------------------------------------------------------------------*/
void anillo(float R, float y, int N) {
	int i;
	float x, z;

	glBegin(GL_LINE_LOOP);
	for (i = 0; i < N; i++) {
		x = dameX(R, N, i);
		z = dameZ(R, N, i);
		glVertex3f(x, y, z);
	}
	glEnd();
}

/*--------------------------------------------------------------------------
 Dibuja una tapa.
 R: el radio del anillo.
 y: la altura a la que se dibuja la tapa.
 N: el n�mero de tramos de la tapa.
 --------------------------------------------------------------------------*/
void tapa(float R, float y, int N, int flip) {
	int i;
	float x, z;

	glPushMatrix();
	glTranslatef(0.0f, y, 0.0f);
	if (flip == true) {
		glRotatef(180, 1.0f, 0.0f, 0.0f);
	}
	glBegin(GL_POLYGON);
	for (i = 0; i < N; i++) {
		x = dameX(R, N, i);
		z = dameZ(R, N, i);
		glVertex3f(x, 0, z);
		glNormal3f(0.0f, 1.0f, 0.0f);
	}
	glEnd();
	glPopMatrix();
}

/*--------------------------------------------------------------------------
 Devuelve un valor de radio en funci�n de la altura
 que se le pasa como par�metro para dibujar un cono.
 y: el valor de la altura.
 --------------------------------------------------------------------------*/
float fCono(float y) {
	return ((y - 0.5f) / 2);
}

/*--------------------------------------------------------------------------
 Dibuja un cono.
 N: el n�mero de anillos y tramos en cada anillo
 --------------------------------------------------------------------------*/
void conoAlambre(int N) {
	formaAlambre(1.0f, N, fCono);
}

/*--------------------------------------------------------------------------
 Dibuja un cono s�lido.
 N: el n�mero de anillos y tramos en cada anillo
 --------------------------------------------------------------------------*/
void conoSolido(int N, int P) {
	formaSolida(1.0f, N, P, fCono);
}

/*--------------------------------------------------------------------------
 Devuelve un valor de radio en funci�n de la altura
 que se le pasa como par�metro para dibujar un cilindro.
 y: el valor de la altura.
 --------------------------------------------------------------------------*/
float fCilindro(float y) {
	return (0.5);
}

/*--------------------------------------------------------------------------
 Dibuja un cilindro.
 N: el n�mero de anillos y tramos en cada anillo
 --------------------------------------------------------------------------*/
void cilindroAlambre(int N) {
	formaAlambre(1.0f, N, fCilindro);
}

/*--------------------------------------------------------------------------
 Dibuja un cilindro s�lido.
 N: el n�mero de anillos y tramos en cada anillo
 --------------------------------------------------------------------------*/
void cilindroSolido(int N, int P) {
	formaSolida(1.0f, N, P, fCilindro);
}

/*--------------------------------------------------------------------------
 Devuelve un valor de radio en funci�n de la altura
 que se le pasa como par�metro para dibujar una esfera.
 y: el valor de la altura.
 --------------------------------------------------------------------------*/
float fEsfera(float y) {
	return ((float) sqrt(fabs(0.25 - (y * y))));
}

/*--------------------------------------------------------------------------
 Dibuja una esfera.
 N: el n�mero de anillos y tramos en cada anillo
 --------------------------------------------------------------------------*/
void esferaAlambre(int N) {
	formaAlambre(1.0f, N, fEsfera);
}

/*--------------------------------------------------------------------------
 Dibuja una esfera s�lida.
 N: el n�mero de anillos y tramos en cada anillo
 --------------------------------------------------------------------------*/
void esferaSolida(int N, int P) {
	formaSolida(1.0f, N, P, fEsfera);
}

/*--------------------------------------------------------------------------
 Devuelve un valor de radio en funci�n de la altura
 que se le pasa como par�metro para dibujar un random.
 y: el valor de la altura.
 --------------------------------------------------------------------------*/
float fRandom(float y) {
	float z = 4 * y * y;
	return ((float) (z * z - z + 0.4f));
}

/*--------------------------------------------------------------------------
 Dibuja una random s�lida.
 N: el n�mero de anillos y tramos en cada anillo
 --------------------------------------------------------------------------*/
void randomSolida(int N, int P) {
	formaSolida(1.0f, N, P, fRandom);
}

/*--------------------------------------------------------------------------
 Algoritmo de dibujo para una figura de revoluci�n.
 H: la altura de la figura.
 N: el n�mero de anillos y divisiones en cada anillo.
 f(float y): puntero a la funci�n que nos devuelve.
 el valor de radio en funci�n de y.
 --------------------------------------------------------------------------*/
void formaAlambre(float H, int N, float (*f)(float y)) {
	int i;
	float y, r;

	for (i = 0; i < N; i++) { // Para cada nivel de altura
		y = i * H / (N - 1) - (H / 2);
		r = f(y);            // obtenemos el radio
		anillo(r, y, N);     // y dibujamos un anillo
	}
}

/*--------------------------------------------------------------------------
 Para hacer cubos
 --------------------------------------------------------------------------*/
void cubo(float W, float H, float n) {
	glPushMatrix();
	glBegin(GL_QUADS);
	glNormal3f(-0.1f, 1, -0.1f);
	glTexCoord2f(0.0, 0.0);
	glVertex3f(-W / 2, -H / 2 * n, -W / 2);
	glNormal3f(0.1f, 1, -0.1f);
	glTexCoord2f(1.0, 0.0);
	glVertex3f(W / 2, -H / 2 * n, -W / 2);
	glNormal3f(0.1f, 1, 0.1f);
	glTexCoord2f(1.0, 1.0);
	glVertex3f(W / 2, -H / 2 * n, W / 2);
	glNormal3f(-0.1f, 1, 0.1f);
	glTexCoord2f(0.0, 1.0);
	glVertex3f(-W / 2, -H / 2 * n, W / 2);
	glEnd();
	glBegin(GL_QUADS);
	glNormal3f(1.0f, 0.0f, 0.0f);
	glTexCoord2f(0.0, 0.0);
	glVertex3f(-W / 2 * n, -H / 2, W / 2);
	glTexCoord2f(1.0, 0.0);
	glVertex3f(-W / 2 * n, H / 2, W / 2);
	glTexCoord2f(1.0, 1.0);
	glVertex3f(-W / 2 * n, H / 2, -W / 2);
	glTexCoord2f(0.0, 1.0);
	glVertex3f(-W / 2 * n, -H / 2, -W / 2);
	glEnd();
	glBegin(GL_QUADS);
	glNormal3f(0.0f, 0.0f, 1.0f);
	glTexCoord2f(0.0, 0.0);
	glVertex3f(-W / 2, -H / 2, -W / 2 * n);
	glTexCoord2f(1.0, 0.0);
	glVertex3f(-W / 2, H / 2, -W / 2 * n);
	glTexCoord2f(1.0, 1.0);
	glVertex3f(W / 2, H / 2, -W / 2 * n);
	glTexCoord2f(0.0, 1.0);
	glVertex3f(W / 2, -H / 2, -W / 2 * n);
	glEnd();
	glBegin(GL_QUADS);
	glNormal3f(0.0f, 1.0f, 0.0f);
	glVertex3f(-W / 2, H / 2 * n, -W / 2);
	glVertex3f(-W / 2, H / 2 * n, W / 2);
	glVertex3f(W / 2, H / 2 * n, W / 2);
	glVertex3f(W / 2, H / 2 * n, -W / 2);
	glEnd();
	glBegin(GL_QUADS);
	glNormal3f(-1.0f, 0.0f, 0.0f);
	glVertex3f(W / 2 * n, -H / 2, -W / 2);
	glVertex3f(W / 2 * n, H / 2, -W / 2);
	glVertex3f(W / 2 * n, H / 2, W / 2);
	glVertex3f(W / 2 * n, -H / 2, W / 2);
	glEnd();
	glBegin(GL_QUADS);
	glNormal3f(0.0f, 0.0f, -1.0f);
	glVertex3f(W / 2, -H / 2, W / 2 * n);
	glVertex3f(W / 2, H / 2, W / 2 * n);
	glVertex3f(-W / 2, H / 2, W / 2 * n);
	glVertex3f(-W / 2, -H / 2, W / 2 * n);
	glEnd();
	glPopMatrix();
	//glDisable ( GL_TEXTURE_2D);

	glBegin(GL_QUADS); //TOP
	glColor4f(1, 1, 1, 0);
	glNormal3f(-0.1f, 1, 0.1f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-1, 0, 1); //bottom left
	glNormal3f(0.1f, 1, 0.1f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(1, 0, 1); //bottom right
	glNormal3f(0.1f, 1, -0.1f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(1, 0, -1); //top right
	glNormal3f(-0.1f, 1, -0.1f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-1, 0, -1); //top left
	glEnd();
}

void bolaPinchos(int n_puas, float factor) {
	float inc_alpha = 360.0f / n_puas;
	float radio = cos(inc_alpha / 360 * 3.141592);

	Cromo(100.0f);
	glPushMatrix();
	glScalef(0.2f * n_puas * factor, 0.2f * n_puas * factor,
			0.2f * n_puas * factor);
	esferaSolida(M, M);
	glPopMatrix();

	Plata(100.0f);
	for (int j = 0; j < n_puas; j++) {
		glPushMatrix();
		glRotatef(j * inc_alpha, 0, 1.0f, 0);
		for (int i = 0; i < (n_puas - 1) / 2 + 2; i++) {
			glPushMatrix();
			glRotatef(i * inc_alpha, 0, 0, 1.0f);
			glTranslatef(0, radio * factor, 0);
			glScalef(3.0f * factor / n_puas, 3.0f * factor / n_puas,
					3.0f * factor / n_puas);
			conoSolido(M, M);
			glPopMatrix();
		}
		glPopMatrix();
	}
}
/*--------------------------------------------------------------------------
 Aqu� es donde se debe crear toda la escena.
 --------------------------------------------------------------------------*/
void escena_old(void) {
	bolaPinchos(8, 0.25);
	Cobre(100.0f);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, dameTextura(0));
	cubo(6.0f, 6.0f, -1.0f);
	glDisable(GL_TEXTURE_2D);
}

/**
 * Escena anterior
 */
void escena(void) {

	Plata(100.0f);
	glPushMatrix();
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, dameTextura(0));
	glTranslatef(-1.0f, 1.2f, 0);
	cilindroSolido(M, M);
	glDisable(GL_TEXTURE_2D);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(0.0f, 1.2f, 0);
	conoSolido(M, M);
	glPopMatrix();

	glPushMatrix();
	glTranslatef(1.0f, 1.2f, 0);
	esferaSolida(M, M);
	glPopMatrix();

	for (int i = 0; i < 3; i++) {
		Cobre(i * 40.0f + 20.0f);
		glPushMatrix();
		glTranslatef(-1.0f + i, 0.0f, 0);
		glRotatef(i * 20.0f, 1.0f, 0.5f, 0.25f);
		randomSolida(M, M);
		glPopMatrix();
	}

	for (int i = 0; i < 3; i++) {
		Cromo(i * 40.0f + 20.0f);
		glPushMatrix();
		glTranslatef(-1.0f + i, -1.2f, 0);
		conoSolido(M, M);
		glPopMatrix();
	}
}
